package com.shortlink.openserver.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.shortlink.common.base.Result;
import com.shortlink.common.base.errcode.BaseErrorCode;
import com.shortlink.common.util.RBloomFilterUtils;
import com.shortlink.openserver.mq.ShortLinkStatsRecordDTO;
import com.shortlink.openserver.mq.ShortLinkStatsSaveProducer;
import com.shortlink.openserver.remote.ShortLinkMapperRemoteService;
import com.shortlink.openserver.service.OpenServerService;
import com.shortlink.openserver.util.HttpUtils;
import com.shortlink.openserver.util.JwtUtils;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.redisson.api.RBloomFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.shortlink.common.constant.RedisKeyConstant.*;

/**
 * 短链接开放服务实现层
 *
 * @author wp
 */
@Service
@RequiredArgsConstructor
public class OpenServerServiceImpl implements OpenServerService {
    private static final String UV_TOKEN = "uv_token";
    private final RBloomFilterUtils bloomFilterUtils;
    private final StringRedisTemplate stringRedisTemplate;
    private final ShortLinkMapperRemoteService shortLinkMapperRemoteService;
    private final JwtUtils jwtUtils;
    private final ShortLinkStatsSaveProducer shortLinkStatsSaveProducer;

    @SneakyThrows
    @Override
    public void restoreUrl(String shortUri, HttpServletRequest request, HttpServletResponse response) {
        String domain = request.getServerName();
        String originUrl = stringRedisTemplate.opsForValue().get(String.format(SHORT_LINK_MAPPER_KEY, domain, shortUri));
        if (ObjectUtil.isNotEmpty(originUrl)) {
            shortLinkStats(request, response, shortUri);
            response.sendRedirect(originUrl);
            return;
        }

        RBloomFilter<String> bloomFilter = bloomFilterUtils.create(String.format(DOMAIN_SHORT_URI_BLOOM_KEY, domain));
        boolean contains = bloomFilter.contains(shortUri);

        if (!contains) {
            response.sendRedirect("/page/notfound");
            return;
        }

        boolean isInNull = !ObjectUtil.isNull(stringRedisTemplate.opsForValue().get(String.format(NULL_SHORT_LINK_MAPPER_KEY, domain, shortUri)));
        if (isInNull) {
            response.sendRedirect("/page/notfound");
            return;
        }

        Result<String> result = shortLinkMapperRemoteService.getOriginByShortLink(domain, shortUri);
        if (!result.isSuccess()) {
            if (BaseErrorCode.FLOW_LIMIT_ERROR.code().equals(result.getCode())) {
                response.sendRedirect("/page/error/busy");
                return;
            }
            response.sendRedirect("/page/error/service");
        }

        originUrl = result.getData();
        if (originUrl == null) {
            stringRedisTemplate.opsForValue().set(String.format(NULL_SHORT_LINK_MAPPER_KEY, domain, shortUri), "-", NULL_SHORT_LINK_MAPPER_TTL, TimeUnit.MINUTES);
            response.sendRedirect("/page/notfound");
            return;
        }
        stringRedisTemplate.opsForValue().set(
                String.format(SHORT_LINK_MAPPER_KEY, domain, shortUri),
                originUrl,
                SHORT_LINK_MAPPER_TTL, TimeUnit.MILLISECONDS
        );
        shortLinkStats(request, response, shortUri);
        response.sendRedirect(originUrl);
    }

    private void shortLinkStats(HttpServletRequest request, HttpServletResponse response, String shortUri) {
        AtomicBoolean uvFlag = new AtomicBoolean(false);
        Cookie[] cookies = request.getCookies();
        AtomicReference<String> uvId = new AtomicReference<>();
        AtomicReference<String> newToken = new AtomicReference<>();
        Runnable addResponseCookieTask = () -> {
            if (!uvFlag.get()) {
                uvId.set(UUID.fastUUID().toString());
                newToken.set(jwtUtils.generateToken(uvId.get()));
            }
            Cookie uvCookie = new Cookie(UV_TOKEN, newToken.get());
            uvCookie.setPath(String.format("/%s", shortUri));
            uvCookie.setMaxAge((int)(jwtUtils.getJwtExpiration() / 1000));
            response.addCookie(uvCookie);
        };
        if (ArrayUtil.isNotEmpty(cookies)) {
            Arrays.stream(cookies)
                    .filter(each -> Objects.equals(each.getName(), UV_TOKEN))
                    .findFirst()
                    .map(Cookie::getValue)
                    .ifPresent(token -> {
                        try {
                            uvId.set(jwtUtils.parseToken(token));
                            uvFlag.set(true);
                            newToken.set(jwtUtils.generateToken(uvId.get()));
                        } catch (Exception ignored) {
                        }
                    });
        }
        addResponseCookieTask.run();

        String domain = request.getServerName();
        String ip = HttpUtils.getActualIp(request);
        String os = HttpUtils.getOs(request);
        String browser = HttpUtils.getBrowser(request);
        String device = HttpUtils.getDevice(request);
        String network = HttpUtils.getNetwork(request);
        ShortLinkStatsRecordDTO shortLinkStatsRecord = ShortLinkStatsRecordDTO.builder()
                .domain(domain)
                .shortUri(shortUri)
                .uvId(uvId.get())
                .uvType(uvFlag.get() ? 1 : 0)
                .ip(ip)
                .os(os)
                .browser(browser)
                .device(device)
                .network(network)
                .visitDate(LocalDate.now())
                .build();
        shortLinkStatsSaveProducer.send(shortLinkStatsRecord);
    }
}
